﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Web.Security;
using System.Configuration.Provider;
using System.Configuration;
using System.Web.Configuration;
using System.Security.Cryptography;
using System.Reflection;
using System.IO;

namespace AnyRepMembershipProvider
{
    public class AnyRepMembership : MembershipProvider
    {
        private const string ANY_REP_MEMBERSHIP_PROVIDER_DEFAULT_NAME = "AnyRepMembershipProvider";

        #region Fields

        /// <summary>
        /// Used when determining encryption key values
        /// </summary>
        private MachineKeySection _machineKey;

        /// <summary>
        /// The field for the repository object use throughtout the class implementation
        /// </summary>
        private IAnyRepMembershipRepository repository;

        /// <summary>
        /// Represents the configuration for the Current Provider Instance
        /// </summary>
        private IAnyRepMembershipConfig providerConfig;

        /// <summary>
        /// A field for the MembershipUserHelpers instance
        /// </summary>
        private IMembershipUserHelpers membershipUserHelpers;

        #endregion

        #region Properties

        public override string PasswordStrengthRegularExpression 
        {
            get { return providerConfig.PasswordStrengthRegularExpression; }
        }

        public override int MinRequiredNonAlphanumericCharacters
        {
            get { return providerConfig.MinRequiredNonAlphanumericCharacters; }
        }

        public override int MinRequiredPasswordLength
        {
            get { return providerConfig.MinRequiredPasswordLength; }
        }

        public override MembershipPasswordFormat PasswordFormat
        {
            get { return providerConfig.PasswordFormat; }
        }

        public override bool RequiresUniqueEmail
        {
            get { return providerConfig.RequiresUniqueEmail; }
        }

        public override int PasswordAttemptWindow
        {
            get { return providerConfig.PasswordAttemptWindow; }
        }

        public override int MaxInvalidPasswordAttempts
        {
            get { return providerConfig.MaxInvalidPasswordAttempts; }
        }

        public override string ApplicationName
        {
            get { return providerConfig.ApplicationName; }
            set { providerConfig.ApplicationName = value; }
        }

        public override bool RequiresQuestionAndAnswer
        {
            get { return providerConfig.RequiresQuestionAndAnswer; }
        }

        public override bool EnablePasswordReset
        {
            get { return providerConfig.EnablePasswordReset; }
        }

        public override bool EnablePasswordRetrieval
        {
            get { return providerConfig.EnablePasswordRetrieval; }
        }



        #endregion

        private string _name;
        public override string Name
        {
            get { return _name; }
        }

        #region Initialization

        public AnyRepMembership()
        {
        }

        public AnyRepMembership(string name, IAnyRepMembershipConfig config, IMembershipUserHelpers membershipUserHelpers)
        {
            if (name == null || name.Length == 0)
                _name = ANY_REP_MEMBERSHIP_PROVIDER_DEFAULT_NAME;
            else
                _name = name;

            base.Initialize(Name, null);

            this.providerConfig = config;
            this.membershipUserHelpers = membershipUserHelpers;

            InitializeRepository();
            InitializeMachineKey();
        }

        public override void Initialize(string name, NameValueCollection config)
        {
            if (config == null)
                throw new ArgumentNullException("config");

            if (name == null || name.Length == 0)
                _name = ANY_REP_MEMBERSHIP_PROVIDER_DEFAULT_NAME;
            else
                _name = name;

            base.Initialize(Name, config);

            providerConfig = new AnyRepMembershipConfig(config);

            InitializeRepository();
            InitializeMachineKey();
        }

        /// <summary>
        /// Initilizes the repository object
        /// </summary>
        private void InitializeRepository()
        {
            Assembly repositoryAssembly;
            if (string.IsNullOrWhiteSpace(providerConfig.RepositoryAssemblyName))
                repositoryAssembly = Assembly.GetExecutingAssembly();
            else
            {
                try
                {
                    repositoryAssembly = Assembly.Load(providerConfig.RepositoryAssemblyName);
                }
                catch (FileNotFoundException ex)
                {
                    throw new ProviderException(string.Format("Could not load Repository Assembly: {0}. Check if the name is correct, if the assembly is present on GAC or in the BIN directory", ex.FileName));
                }
            }

            Type repositoryClassType = repositoryAssembly.GetType(providerConfig.RepositoryClassName);
            // TODO: Add Logic to check if the Type implements the needed interface

            ConstructorInfo repositoryConstructor = repositoryClassType.GetConstructor(System.Type.EmptyTypes);
            if (repositoryConstructor != null)
                repository = repositoryConstructor.Invoke(null) as IAnyRepMembershipRepository;

            repository.ApplicationName = providerConfig.ApplicationName;
        }

        /// <summary>
        /// Initilizes machine key field used for encrypted passwords
        /// </summary>
        private void InitializeMachineKey()
        {
            // Get encryption and decryption key information from the configuration
            Configuration configuration = WebConfigurationManager.OpenWebConfiguration(System.Web.Hosting.HostingEnvironment.ApplicationVirtualPath);
            _machineKey = configuration.GetSection("system.web/machineKey") as MachineKeySection;

            if (_machineKey.ValidationKey.Contains("AutoGenerate"))
                if (PasswordFormat != MembershipPasswordFormat.Clear)
                    throw new ProviderException("Hashed or Encrypted password are not supported with auto-generated keys.");
        }

        #endregion

        public override bool ChangePassword(string username, string oldPassword, string newPassword)
        {
            throw new NotImplementedException();
        }

        public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
        {
            throw new NotImplementedException();
        }

        public override MembershipUser CreateUser(
            string username,
            string password,
            string email,
            string passwordQuestion,
            string passwordAnswer,
            bool isApproved,
            object providerUserKey,
            out MembershipCreateStatus status)
        {
            // Password Validation
            ValidatePasswordEventArgs args = new ValidatePasswordEventArgs(username, password, true);
            OnValidatingPassword(args);

            if (args.Cancel)
            {
                status = MembershipCreateStatus.InvalidPassword;
                return null;
            }

            if (RequiresUniqueEmail && !string.IsNullOrWhiteSpace(GetUserNameByEmail(email)))
            {
                status = MembershipCreateStatus.DuplicateEmail;
                return null;
            }

            MembershipUser membershipUser = GetUser(username, false);
            if (membershipUser == null)
            {
                Guid userId = Guid.Parse(providerUserKey.ToString());
                var user = new AnyRepMembershipUser
                {
                    UserId = userId,
                    Username = username,
                    Email = email,
                    Password = password,
                    PasswordAnswer = passwordAnswer,
                    PasswordQuestion = passwordQuestion,
                    IsLockedOut = false,
                    IsApproved = isApproved,
                    CreatedDate = DateTime.Now
                };

                // TODO: Additional Error Handling
                repository.AddUser(user);
                status = MembershipCreateStatus.Success;
            }
            else
            {
                status = MembershipCreateStatus.DuplicateUserName;
                return null;
            }

            status = MembershipCreateStatus.Success;
            return GetUser(username, false);
        }

        public override bool DeleteUser(string username, bool deleteAllRelatedData)
        {
            throw new NotImplementedException();
        }

        public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
        {
            throw new NotImplementedException();
        }

        public override int GetNumberOfUsersOnline()
        {
            throw new NotImplementedException();
        }

        public override string GetPassword(string username, string answer)
        {
            throw new NotImplementedException();
        }

        public override MembershipUser GetUser(string username, bool userIsOnline)
        {
            AnyRepMembershipUser user = repository.GetUserByUsername(username);
            if (user != null)
            {
                if (userIsOnline)
                    repository.UpdateUserLastActivity(user);

                return membershipUserHelpers.GetMembershipUserFromAnyRepMembershipUser(Name, user);
            }

            return null;
        }

        public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
        {
            throw new NotImplementedException();
        }

        public override string GetUserNameByEmail(string email)
        {
            AnyRepMembershipUser user = repository.GetUserByEmail(email);
            if (user != null)
                return user.Username;

            return null;
        }

        public override string ResetPassword(string username, string answer)
        {
            throw new NotImplementedException();
        }

        public override bool UnlockUser(string userName)
        {
            throw new NotImplementedException();
        }

        public override void UpdateUser(MembershipUser user)
        {
            throw new NotImplementedException();
        }

        public override bool ValidateUser(string username, string password)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Returns the encoded password based on the configured Password Format
        /// </summary>
        private string EncodePassword(string password)
        {
            string encodedPassword = password;

            switch (PasswordFormat)
            {
                case MembershipPasswordFormat.Clear:
                    break;
                case MembershipPasswordFormat.Encrypted:
                    encodedPassword = Convert.ToBase64String(EncryptPassword(Encoding.Unicode.GetBytes(password)));
                    break;
                case MembershipPasswordFormat.Hashed:
                    HMACSHA1 hash = new HMACSHA1();
                    hash.Key = HexToByte(_machineKey.ValidationKey);
                    encodedPassword = Convert.ToBase64String(hash.ComputeHash(Encoding.Unicode.GetBytes(password)));
                    break;
                default:
                    throw new ProviderException("Unsupported password format.");
            }

            return encodedPassword;
        }

        /// <summary>
        /// Converts a hexadecimal string to a byte array.
        /// Used to convert encryption key values from the configuration.
        /// </summary>
        private byte[] HexToByte(string hexString)
        {
            byte[] returnBytes = new byte[hexString.Length / 2];
            for (int i = 0; i < returnBytes.Length; i++)
                returnBytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);

            return returnBytes;
        }
    }
}
